// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: jaewon

#pragma once

#include <algorithm>
#include <memory>
#include <numeric>
#include <vector>

#include <glog/logging.h>

namespace coin2::base::request {
namespace detail {
template <typename T, typename Allocator = std::allocator<T>>
class SimpleObjectPool {
 public:
  SimpleObjectPool() = default;
  ~SimpleObjectPool() {
    size_t chunk_size = kInitChunkSize;
    for (auto* chunk : objs_) {
      allocator_.deallocate(chunk, chunk_size);
      chunk_size *= 2;
    }
    objs_.clear();
  }

  template <typename... Args>
  std::shared_ptr<T> Alloc(Args... args) {
    if (unused_.empty()) {
      AddChunk();
    }
    T* obj{unused_.back()};

    new (obj) T{std::forward<Args>(args)...};

    unused_.pop_back();
    return std::shared_ptr<T>(obj, [this](T* object) {
      std::_Destroy(object);
      unused_.push_back(object);
    });
  }

 private:
  void AddChunk() {
    // Apply for an unconstructed array obj(size)
    auto* obj{allocator_.allocate(size_)};
    objs_.emplace_back(obj);

    // Fill
    auto unused_size = unused_.size();
    unused_.resize(unused_size + size_);
    std::iota(std::begin(unused_) + unused_size, std::end(unused_), obj);

    size_ *= 2;
  }

 private:
  static const size_t kInitChunkSize = 8;

 private:
  std::vector<T*> unused_;
  std::vector<T*> objs_;
  size_t size_ = kInitChunkSize;
  Allocator allocator_;
};

}  // namespace detail
}  // namespace coin2::base::request
